﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Xceed.Silverlight.Data;
using Xceed.Silverlight.DataGrid.Export;
using Xceed.Silverlight.Utils;

namespace Xceed.Silverlight.DataGrid
{
  public partial class DataGridControl
  {
    private class HardwareEventsManager
    {
      public HardwareEventsManager(DataGridControl dataGrid)
      {
        m_dataGrid = dataGrid;
        m_dataGrid.EventManager.AddHandler( RootHost.RootKeyDownEvent, this.OnRootContainerKeyDown );
        m_dataGrid.EventManager.AddHandler( EditableRow.EditableRowMouseLeftButtonDownEvent, this.OnEditableRowMouseLeftButtonDown );
        m_dataGrid.EventManager.AddHandler( Row.RowMouseLeftButtonDownEvent, this.OnRowMouseLeftButtonDown );
        m_dataGrid.EventManager.AddHandler( Row.RowMouseLeftButtonUpEvent, this.OnRowMouseLeftButtonUp );
        m_dataGrid.EventManager.AddHandler( EditableCell.EditableCellMouseLeftButtonDownEvent, this.OnEditableCellMouseLeftButtonDown );
        m_dataGrid.EventManager.AddHandler( Cell.CellMouseLeftButtonDownEvent, this.OnCellMouseLeftButtonDown );
        m_dataGrid.EventManager.AddHandler( Cell.CellMouseLeftButtonUpEvent, this.OnCellMouseLeftButtonUp );
        m_dataGrid.EventManager.AddHandler( ColumnManagerCell.ColumnManagerCellMouseLeftButtonDownEvent, this.OnColumnManagerCellMouseLeftButtonDown );
        m_dataGrid.EventManager.AddHandler( ColumnManagerCell.ColumnManagerCellMouseLeftButtonUpEvent, this.OnColumnManagerCellMouseLeftButtonUp );
        m_dataGrid.EventManager.AddHandler( GroupHeaderControl.GroupMouseLeftButtonDownEvent, this.OnGroupMouseLeftButtonDown );
        m_dataGrid.EventManager.AddHandler( GroupHeaderControl.GroupMouseLeftButtonUpEvent, this.OnGroupMouseLeftButtonUp );
      }

      private void OnColumnManagerCellMouseLeftButtonDown( object sender, XceedRoutedEventArgs args )
      {
        XceedRoutedEventMouseButtonEventArgs mouseArgs = ( XceedRoutedEventMouseButtonEventArgs )args;
        ColumnManagerCell columnManagerCell = ( ColumnManagerCell )args.OriginalSource;

        columnManagerCell.IsPressed = true;

        m_dataGrid.PrepareDrag( columnManagerCell, mouseArgs.OriginalArgs );
      }

      private void OnColumnManagerCellMouseLeftButtonUp( object sender, XceedRoutedEventArgs args )
      {
        ColumnManagerCell columnManagerCell = ( ColumnManagerCell )args.OriginalSource;

        if( columnManagerCell.IsPressed )
        {
          m_dataGrid.SortChangeRequestChanged( columnManagerCell, null );
        }
        columnManagerCell.IsPressed = false;
      }

      private void OnEditableCellMouseLeftButtonDown( object sender, XceedRoutedEventArgs args )
      {
        EditableCell cell = ( EditableCell )args.OriginalSource;

        {
          m_dataGrid.CheckEditTriggers( ( EditableRow )cell.ParentRow, cell, true );
        }

        this.OnCellMouseLeftButtonDown( sender, args );
      }

      private void OnCellMouseLeftButtonDown( object sender, XceedRoutedEventArgs args )
      {
        XceedRoutedEventMouseButtonEventArgs mouseArgs = ( XceedRoutedEventMouseButtonEventArgs )args;
        Cell cell = ( Cell )args.OriginalSource;

        mouseArgs.Handled = this.HandleMouseLeftButtonDown( cell.ParentRow, cell );
      }

      private void OnEditableRowMouseLeftButtonDown( object sender, XceedRoutedEventArgs args )
      {
        EditableRow row = ( EditableRow )args.OriginalSource;

        {
          m_dataGrid.CheckEditTriggers( row, null, true );
        }

        this.OnRowMouseLeftButtonDown( sender, args );
      }

      private void OnRowMouseLeftButtonDown( object sender, XceedRoutedEventArgs args )
      {
        XceedRoutedEventMouseButtonEventArgs mouseArgs = ( XceedRoutedEventMouseButtonEventArgs )args;
        Row row = ( Row )args.OriginalSource;

        mouseArgs.Handled = this.HandleMouseLeftButtonDown( row, null );
      }

      private void OnGroupMouseLeftButtonDown( object sender, XceedRoutedEventArgs args )
      {
        XceedRoutedEventMouseButtonEventArgs mouseArgs = ( XceedRoutedEventMouseButtonEventArgs )args;
        GroupHeaderControl group = ( GroupHeaderControl )args.OriginalSource;

        mouseArgs.Handled = this.HandleMouseLeftButtonDown( group.GroupPath, null );
      }

      private void OnCellMouseLeftButtonUp( object sender, XceedRoutedEventArgs args )
      {
        XceedRoutedEventMouseButtonEventArgs mouseArgs = ( XceedRoutedEventMouseButtonEventArgs )args;
        Cell cell = ( Cell )args.OriginalSource;

        mouseArgs.Handled = this.HandleMouseLeftButtonUp( cell.ParentRow, cell );
      }

      private void OnRowMouseLeftButtonUp( object sender, XceedRoutedEventArgs args )
      {
        XceedRoutedEventMouseButtonEventArgs mouseArgs = ( XceedRoutedEventMouseButtonEventArgs )args;
        Row row = ( Row )args.OriginalSource;

        mouseArgs.Handled = this.HandleMouseLeftButtonUp( row, null );
      }

      private void OnGroupMouseLeftButtonUp( object sender, XceedRoutedEventArgs args )
      {
        XceedRoutedEventMouseButtonEventArgs mouseArgs = ( XceedRoutedEventMouseButtonEventArgs )args;
        GroupHeaderControl group = ( GroupHeaderControl )args.OriginalSource;

        mouseArgs.Handled = this.HandleMouseLeftButtonUp( group.GroupPath, null );
      }

      private bool HandleMouseLeftButtonDown( IDataGridElement dataGridElement, IColumnElement columnElement )
      {
        DataPath path = (dataGridElement == null) ? null : dataGridElement.Path;
        return this.HandleMouseLeftButtonDown( path, columnElement );
      }

      private bool HandleMouseLeftButtonDown( DataPath elementPath, IColumnElement columnElement )
      {
        m_updateSelectionOnMouseUp = null;

        if( elementPath == null )
          return false;

        if( elementPath.IsStubPath() )
          return false;

        object currentElement = m_dataGrid.SetCurrentElement( elementPath, columnElement );

        if( currentElement is Control )
        {
          ( ( Control )currentElement ).EnsureHasFocus( null );
        }

        if( !elementPath.IsGroupPath() && m_dataGrid.IsPathSelected( elementPath ) )
        {
          m_updateSelectionOnMouseUp = elementPath;
        }
        else
        {
          bool clearSelection = !this.IsModifierPressed( ModifierKeys.Control );
          bool updateAnchor = !this.IsModifierPressed( ModifierKeys.Shift );
          m_dataGrid.ApplySelectionUpdate( this.CreateMouseSelectionUpdate( clearSelection, updateAnchor ) );
        }

        return true;
      }

      private bool HandleMouseLeftButtonUp( IDataGridElement element, IColumnElement columnElement )
      {
        DataPath path = (element == null) ? null : element.Path;
        return this.HandleMouseLeftButtonUp( path, columnElement );
      }


      private bool HandleMouseLeftButtonUp( DataPath elementPath, IColumnElement columnElement )
      {
        if( m_updateSelectionOnMouseUp != null
          && object.Equals( m_updateSelectionOnMouseUp, elementPath ) )
        {
          bool clearSelection = !this.IsModifierPressed( ModifierKeys.Control );
          bool updateAnchor = !this.IsModifierPressed( ModifierKeys.Shift );
          m_dataGrid.ApplySelectionUpdate( this.CreateMouseSelectionUpdate( clearSelection, updateAnchor ) );
          return true;
        }

        m_updateSelectionOnMouseUp = null;
        return false;
      }


      private void OnRootContainerKeyDown( object sender, XceedRoutedEventArgs e )
      {
        XceedRoutedEventKeyEventArgs args = ( XceedRoutedEventKeyEventArgs )e;
        if( args.Handled )
          return;

        // Do not handle any key if the input comes from a descendant of a Cell.
        DependencyObject source = args.OriginalArgs.OriginalSource as DependencyObject;
        if( (source != null) && (args.Key != Key.Tab) )
        {
          if( source.GetAncestors().Any( ( ancestor ) => ancestor is Cell ) )
            return;
        }

        if( this.HandleRootContainerKeyDown( args.Key ) )
        {
          args.Handled = true;
        }
      }

      private bool HandleRootContainerKeyDown( Key key )
      {
        return this.HandleInvalidKeyEvent( key )
        ||this.HandleKeyboardSpace( key )
        ||this.HandleSelectAll( key )
        ||this.HandleRowHomeEndNavigation( key )
        ||this.HandleColumnHomeEndNavigation( key )
        ||this.HandleRowNavigation( key )
        ||this.HandleColumnNavigation( key )
        ||this.HandleCopyToClipBoard( key )
        ||this.HandleColumnTabNavigation( key );
      }

      private bool HandleInvalidKeyEvent( Key key )
      {
        // Ignore any key presses with "special" modifiers. 
        // (e.g., "Windows", "Apple", "Alt")
        return this.IsModifierPressed( ModifierKeys.Alt )
#if SILVERLIGHT
          || this.IsModifierPressed( ModifierKeys.Apple )
#endif
          || this.IsModifierPressed( ModifierKeys.Windows );
      }

      private bool HandleSelectAll( Key key )
      {
        if( ( key == Key.A ) && ( Keyboard.Modifiers == ModifierKeys.Control ) )
        {
          if( m_dataGrid.SelectionMode != System.Windows.Controls.SelectionMode.Single )
          {
            m_dataGrid.SelectAllHelper();
          }

          return true;
        }

        return false;
      }

      private bool HandleCopyToClipBoard( Key key )
      {
        if( ( key == Key.C ) && this.IsModifierPressed( ModifierKeys.Control ) )
        {
          if( !m_dataGrid.DataGridContext.DataSource.Provider.IsAsynchronous )
          {
            switch( m_dataGrid.ClipboardCopyMode )
            {
              case DataGridClipboardCopyMode.None:
                return false; 

              case DataGridClipboardCopyMode.IncludeHeader:
              case DataGridClipboardCopyMode.ExcludeHeader:
                {
                  CsvExporter clipboardExporter = new CsvExporter( m_dataGrid );

                  //Set properties that are null by default.
                  clipboardExporter.FormatSettings.Culture = CultureInfo.CurrentCulture;
                  clipboardExporter.FormatSettings.DateTimeFormat = "d";
                  clipboardExporter.FormatSettings.NumericFormat = "g";
                  clipboardExporter.FormatSettings.Separator = '\t';  //Using tab, for excel to work

                  clipboardExporter.IncludeColumnHeaders = m_dataGrid.ClipboardCopyMode == DataGridClipboardCopyMode.IncludeHeader;
                  string clipboardText = clipboardExporter.ExportToText( m_dataGrid.SelectedRanges );

                  // Copy text to the clipboard.
                  Clipboard.SetText( clipboardText );

                  return true;
                }
            }
          }
        }

        return false;
      }

      private bool HandleKeyboardSpace( Key key )
      {
        if( key != Key.Space )
          return false;

          bool clearSelection = !this.IsModifierPressed( ModifierKeys.Control );
          bool updateAnchor = !this.IsModifierPressed( ModifierKeys.Shift );
          m_dataGrid.ApplySelectionUpdate( this.CreateKeyboardSelectionUpdate( clearSelection, updateAnchor, true ) );

          return true;
        }

      private bool HandleRowNavigation( Key key )
      {
        NavigationOperation? navigation = this.ConvertToRowNavigationOperation( key );
        if( navigation != null )
        {
          bool cleanSelection = !this.IsModifierPressed( ModifierKeys.Control );
          bool updateAnchor = !this.IsModifierPressed( ModifierKeys.Shift );
          SelectionUpdate selectionUpdate = this.CreateKeyboardSelectionUpdate( cleanSelection, updateAnchor, false );

          //Note: a PageUp or PageDown combined with a Ctrl is handled as a Home or End without affecting the
          //current column. There is no such thing as a PageUp or PageDown that doesn't clear selection.
          //The Ctrl+PageXXX operation is handled in HandleHomeEndNavigation
          Debug.Assert( cleanSelection || !key.IsPage() );
          UpdateRequest neededUpdate = m_dataGrid.m_rowUpdateManager.Navigate( navigation.Value );
          if( neededUpdate != null )
          {
            neededUpdate.UpdateDetails.SelectionUpdateToApply = selectionUpdate;
            m_dataGrid.SetData( neededUpdate );
          }
          else
          {
            m_dataGrid.ApplySelectionUpdate( selectionUpdate );
          }
          return true;
        }

        return false;
      }

      private bool HandleRowHomeEndNavigation( Key key )
      {
        // Ctrl+Home   : Bring to first row and first column.
        // Ctrl+PageUp : Bring to the first row. Keep same column.
        // Some concept applies for the "End" and "PageDown" buttons
        //
        // Always clear the selection.
        // Shift keeps the same selection anchor. As usual.

        if( !this.IsModifierPressed( ModifierKeys.Control ) )
          return false;

        if( key == Key.Home || key == Key.PageUp || key == Key.End || key == Key.PageDown )
        {
          bool updateAnchor = !this.IsModifierPressed( ModifierKeys.Shift );
          SelectionUpdate selectionUpdate = this.CreateKeyboardSelectionUpdate( true, updateAnchor, false );

          UpdateRequest neededUpdate = null;
          if( key == Key.Home || key == Key.PageUp )
          {
            neededUpdate = m_dataGrid.m_rowUpdateManager.NavigateToFirst();
          }
          else
          {
            neededUpdate = m_dataGrid.m_rowUpdateManager.NavigateToLast();
          }

          Debug.Assert( neededUpdate != null );

          neededUpdate.UpdateDetails.SelectionUpdateToApply = selectionUpdate;

          m_dataGrid.SetData( neededUpdate );

          return !key.IsHomeEnd();
        }

        return false;
      }

      private bool HandleColumnHomeEndNavigation( Key key )
      {
        // Ctrl+Home   : Bring to first row and first column.
        // Ctrl+End    : Bring to last row and last column.
        // Home        : Bring to first column.
        // End         : Bring to last column.
        //
        // Do not affect selection.
        //

        UpdateRequest neededUpdate = null;

        if( key == Key.Home )
        {
          neededUpdate = m_dataGrid.m_columnUpdateManager.NavigateToFirst();
        }
        else if(key == Key.End)
        {
          neededUpdate = m_dataGrid.m_columnUpdateManager.NavigateToLast();
        }

        if( neededUpdate != null )
        {
          m_dataGrid.SetColumns( neededUpdate );
        }

        return key.IsHomeEnd();
      }

      private bool HandleColumnNavigation( Key key )
      {
        NavigationOperation? navigation = this.ConvertToColumnNavigationOperation( key );
        if( navigation != null )
        {
          UpdateRequest neededUpdate = m_dataGrid.m_columnUpdateManager.Navigate( navigation.Value );

          if( neededUpdate != null )
          {
            m_dataGrid.SetColumns( neededUpdate );
          }

          return true;
        }

        return false;
      }

      private bool HandleColumnTabNavigation( Key key )
      {
        if( ( key == Key.Tab ) && ( m_dataGrid.CurrentElement is Row ) )
        {
          Column currentColumn = m_dataGrid.CurrentColumn;
          Column nextCurrentColumn = null;

          if( this.IsModifierPressed( ModifierKeys.Shift ) )
          {
            nextCurrentColumn = this.GetPreviousColumn( currentColumn.VisiblePosition );
          }
          else
          {
            nextCurrentColumn = this.GetNextColumn( currentColumn.VisiblePosition );
          }

          Debug.Assert( nextCurrentColumn != null );
          m_dataGrid.CurrentColumn = nextCurrentColumn;
          m_dataGrid.BringColumnIntoView( nextCurrentColumn );

          return true;
        }

        return false;
      }

      private Column GetNextColumn( int startIndex )
      {
        List<Column> columns = m_dataGrid.GetScrollableColumns().ToList();
        int count = columns.Count;

        if( count == 0 )
          return null;

        int index = Math.Max( 0, startIndex + 1 );
        if( index >= count )
          return columns.First();

        return columns[ index ];
      }

      private Column GetPreviousColumn( int startIndex )
      {
        List<Column> columns = m_dataGrid.GetScrollableColumns().ToList();
        int count = columns.Count;

        if( count == 0 )
          return null;

        int index = Math.Min( count - 1, startIndex - 1 );
        if( index < 0 )
          return columns.Last();

        return columns[ index ];
      }

      internal SelectionUpdate CreateMouseSelectionUpdate( bool clearSelection, bool updateAnchor )
      {
        if( m_dataGrid.SelectionMode == System.Windows.Controls.SelectionMode.Extended )
        {
          return this.CreateMouseSelectionUpdateExtended( clearSelection, updateAnchor );
        }
        else if( m_dataGrid.SelectionMode == System.Windows.Controls.SelectionMode.Multiple )
        {
          return this.CreateMouseSelectionUpdateMultiple( clearSelection, updateAnchor );
        }
        else
        {
          Debug.Assert( m_dataGrid.SelectionMode == System.Windows.Controls.SelectionMode.Single );
          return this.CreateMouseSelectionUpdateSimple( clearSelection, updateAnchor );
        }
      }

      internal SelectionUpdate CreateKeyboardSelectionUpdate( bool clearSelection, bool updateAnchor, bool isSpace )
      {
        if( m_dataGrid.SelectionMode == System.Windows.Controls.SelectionMode.Extended )
        {
          return this.CreateKeyboardSelectionUpdateExtended( clearSelection, updateAnchor, isSpace );
        }
        else if( m_dataGrid.SelectionMode == System.Windows.Controls.SelectionMode.Multiple )
        {
          return this.CreateKeyboardSelectionUpdateMultiple( clearSelection, updateAnchor, isSpace );
        }
        else
        {
          Debug.Assert( m_dataGrid.SelectionMode == System.Windows.Controls.SelectionMode.Single );
          return this.CreateKeyboardSelectionUpdateSimple( clearSelection, updateAnchor, isSpace );
        }
      }

      internal SelectionUpdate CreateMouseSelectionUpdateExtended( bool clearSelection, bool updateAnchor )
      {
        return new SelectionUpdate( updateAnchor, clearSelection, clearSelection || updateAnchor );
      }

      internal SelectionUpdate CreateKeyboardSelectionUpdateExtended( bool clearSelection, bool updateAnchor, bool isSpace )
      {
        if( !isSpace && !clearSelection && updateAnchor )
            return null;

        bool reverseAnchor = ( isSpace )
          ? updateAnchor || clearSelection
          : clearSelection;

        return new SelectionUpdate( updateAnchor, clearSelection, reverseAnchor );
      }

      internal SelectionUpdate CreateMouseSelectionUpdateMultiple( bool clearSelection, bool updateAnchor )
      {
        return new SelectionUpdate( true, false, true );
      }

      internal SelectionUpdate CreateKeyboardSelectionUpdateMultiple( bool clearSelection, bool updateAnchor, bool isSpace )
      {
        return ( isSpace ) ? new SelectionUpdate( true, false, true ) : null;
      }

      internal SelectionUpdate CreateMouseSelectionUpdateSimple( bool clearSelection, bool updateAnchor )
      {
        return new SelectionUpdate( true, true, true );
      }

      internal SelectionUpdate CreateKeyboardSelectionUpdateSimple( bool clearSelection, bool updateAnchor, bool isSpace )
      {
        if( !isSpace && !clearSelection )
          return null;

        bool clear = ( isSpace ) ? true : clearSelection;
        bool anchor = ( isSpace ) ? true : clear;
        bool reverseAnchor = ( isSpace ) ? true : clear;

        return new SelectionUpdate( anchor, clear, reverseAnchor );
      }

      private NavigationOperation? ConvertToRowNavigationOperation( Key key )
      {
        switch( key )
        {
          case Key.Down:
            return NavigationOperation.NextItem;
          case Key.Up:
            return NavigationOperation.PreviousItem;
          case Key.PageDown:
            return NavigationOperation.NextPage;
          case Key.PageUp:
            return NavigationOperation.PreviousPage;
        }

        return null;
      }

      private NavigationOperation? ConvertToColumnNavigationOperation( Key key )
      {
        switch( key )
        {
          case Key.Right:
            return NavigationOperation.NextItem;
          case Key.Left:
            return NavigationOperation.PreviousItem;
        }

        return null;
      }

      private bool IsModifierPressed( ModifierKeys key )
      {
        return ( Keyboard.Modifiers & key ) == key;
      }

      private DataGridControl m_dataGrid;
      private DataPath m_updateSelectionOnMouseUp;

    }
  }
}
